package com.haibara.codesubmiteval.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.haibara.codesubmiteval.common.ErrorCode;
import com.haibara.codesubmiteval.constant.RedisKeyConstant;
import com.haibara.codesubmiteval.exception.BusinessException;
import com.haibara.codesubmiteval.exception.ThrowUtils;
import com.haibara.codesubmiteval.model.vo.Top10VO;
import com.haibara.codesubmiteval.utils.RedisRecoveryUtil;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * Top10榜单服务
 *
 * @Author: mengyu
 * @Date: 2025/4/29
 */
@Service
public class Top10Service {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    @Lazy
    private RedisRecoveryUtil redisRecoveryUtil;

    /**
     * 新增或修改榜单记录
     * 榜单中只会记录用户id和分数
     *
     * @param userId 用户id
     * @param score  分数
     */
    public void addOrUpdateTop10(Long userId, Integer score) {
        stringRedisTemplate.opsForZSet().add(
                RedisKeyConstant.top10,
                JSONUtil.toJsonStr(Top10VO.builder().userId(userId).build()),
                score
        );
    }

    public List<Top10VO> getTop10Ranking() {
        // 获取题目总分
        String totalQuestionScoreStr = stringRedisTemplate.opsForValue().get(
                RedisKeyConstant.totalQuestionScore
        );
        if (StrUtil.isBlankIfStr(totalQuestionScoreStr)) {
            totalQuestionScoreStr = redisRecoveryUtil.recoveryTotalQuestionScore();
        }
        ThrowUtils.throwIf(StrUtil.isBlankIfStr(totalQuestionScoreStr), ErrorCode.SYSTEM_ERROR, "Redis宕机");
        assert totalQuestionScoreStr != null;
        int totalQuestionScore = Integer.parseInt(totalQuestionScoreStr);
        // 从Redis中获取Top10
        ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<String>> typedTuples = zSetOperations.reverseRangeByScoreWithScores(RedisKeyConstant.top10, 0, totalQuestionScore, 0, 10);
        if (typedTuples == null) {
            typedTuples = redisRecoveryUtil.recoveryTop();
        }
        // 获取系统普通用户id
        Set<String> commonUserIds = stringRedisTemplate.opsForSet().members(RedisKeyConstant.commonUserIds);
        if (commonUserIds == null) {
            commonUserIds = redisRecoveryUtil.recoveryCommonUserIds();
        }
        ThrowUtils.throwIf(commonUserIds == null || commonUserIds.isEmpty(), ErrorCode.SYSTEM_ERROR, "系统无普通用户");
        List<Top10VO> result = new ArrayList<>();
        int rank = 1;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            Top10VO top10VO = JSONUtil.toBean(typedTuple.getValue(), Top10VO.class);
            if (typedTuple.getScore() == null) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "Redis存储完成题目数丢失 : " + top10VO.getUserId());
            }
            if (!commonUserIds.contains(top10VO.getUserId().toString())) {
                continue;
            }
            double score = typedTuple.getScore();
            top10VO.setScore((int) score);
            top10VO.setRank(rank++);
            result.add(top10VO);
        }
        return result;
    }

    public Long getUserRank(Long userId) {
        // {"userId":2106050212}
        Long rank = stringRedisTemplate.opsForZSet().reverseRank(
                RedisKeyConstant.top10, Top10VO.getTop10VOString(userId)
        );
        if (rank == null) {
            redisRecoveryUtil.recoveryUserTop(userId);
            rank = stringRedisTemplate.opsForZSet().reverseRank(
                    RedisKeyConstant.top10, Top10VO.getTop10VOString(userId)
            );
            ThrowUtils.throwIf(rank == null, ErrorCode.SYSTEM_ERROR, "Redis宕机");
        }
        // 获取到的Rank是从0开始，所以需要+1
        return rank + 1;
    }

    public Integer getUserScore(Long userId) {
        // {"userId":2106050212}
        Double score = stringRedisTemplate.opsForZSet().score(
                RedisKeyConstant.top10, Top10VO.getTop10VOString(userId)
        );
        if (score == null) {
            return redisRecoveryUtil.recoveryUserScore(userId);
        }
        return score.intValue();
    }

    public Long getTotalScore() {
        String luaScript =
                """
                                local members = redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')
                                local total = 0
                                for i = 2, #members, 2 do
                                    total = total + tonumber(members[i])
                                end
                                return tostring(total)
                        """;
        RedisScript<String> script = RedisScript.of(luaScript, String.class);
        String result = stringRedisTemplate.execute(script, Collections.singletonList(RedisKeyConstant.top10));
        return Long.parseLong(result);
    }

    public void removeUser(Long userId) {
        stringRedisTemplate.opsForZSet().remove(
                RedisKeyConstant.top10,
                JSONUtil.toJsonStr(
                        Top10VO.builder()
                                .userId(userId)
                                .build()
                )
        );
    }

    public void incrUserScore(Long userId, Integer score) {
        Top10VO top10VO = Top10VO.builder()
                .userId(userId)
                .build();
        stringRedisTemplate.opsForZSet().incrementScore(RedisKeyConstant.top10, JSONUtil.toJsonStr(top10VO), score);
    }

    public void batchIncrUserScore(List<Long> ids, Integer delta) {
        stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Long id : ids) {
                stringRedisTemplate.opsForZSet().incrementScore(
                        RedisKeyConstant.top10,
                        "{\"userId\":" + id + "}",
                        delta
                );
            }
            return null; // Pipeline需要返回null
        });
    }

}
